﻿--[[
	This file is part of FlexBar3.

	FlexBar3 is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	FlexBar3 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with FlexBar3.  If not, see <http://www.gnu.org/licenses/>.
]]
local Button = FlexBar3:GetModule("Button");
local AttributeClass = Button:NewButtonClass("Attribute", 1);
local SecureStateDriverFrame = Button.SecureStateDriverFrame;

-- Iterator function
local function splitNext(sep, body)
    if (body) then
        local pre, post = strsplit(sep, body, 2);
        if (post) then
            return post, pre;
        end
        return false, body;
    end
end

local function SemicolonIterator(str) return splitNext, ";", str; end

function AttributeClass:Load()
	SecureStateDriverFrame:SetAttribute("addchild", self.Frame);
end

function AttributeClass:SetDefaults()
	self.DB.Attributes = {};
	self.DB.Remaps = {};
end

function AttributeClass:LoadSettings()
	self:GenerateStateButton();
end

function AttributeClass:Enable()
	self.ModifiedPrefix = self:GetModifiedPrefix();
	self.ModifiedSuffix = self:GetModifiedSuffix();
	self:RegisterEvent("MODIFIER_STATE_CHANGED", self.RecacheModifiers);
end

function AttributeClass:Disable()
	self:UnregisterEvent("MODIFIER_STATE_CHANGED", self.RecacheModifiers);
end

function AttributeClass:RecacheModifiers()
	local OldPrefix = self.ModifiedPrefix;
	local OldSuffix = self.ModifiedSuffix;
	local NewPrefix = self:GetModifiedPrefix();
	local NewSuffix = self:GetModifiedSuffix();
	if(OldPrefix ~= NewPrefix or OldSuffix ~= NewSuffix) then
		self.ModifiedPrefix = NewPrefix;
		self.ModifiedSuffix = NewSuffix;
		self:TriggerCallback("ModifiersChanged", OldPrefix, OldSuffix, NewPrefix, NewSuffix);
	end
end

function AttributeClass:GetModifiedPrefix()
	return SecureButton_GetModifierPrefix();
end

function AttributeClass:GetModifiedSuffix()
	return SecureButton_GetButtonSuffix(SecureStateChild_GetEffectiveButton(self.Frame));
end

function AttributeClass:GetAttribute(Attribute)
	return self.Frame:GetAttribute(Attribute);
end

function AttributeClass:SetAttribute(Attribute, Value)
	self.Frame:SetAttribute(Attribute, Value);
end

-- Gets an attribute modified with the given prefix and suffix, if no prefix and/or suffix were passed, the ones currently applicable to the button are used
function AttributeClass:GetModifiedAttribute(Attribute, StateModifier)
	-- sanity check, Attribute must be a string
	assert(type(Attribute) == "string" and Attribute ~= "", ("Attribute must be a non-empty string, it was %s"):format(type(Attribute)), 2);
	local ModifiedAttribute;
	if(StateModifier) then
		ModifiedAttribute = StateModifier:format(Attribute);
	else
		local Prefix = self:GetModifiedPrefix();
		ModifiedAttribute = ("%s%s%s"):format(Prefix ~= "" and Prefix or "*", Attribute, self:GetModifiedSuffix());
	end
	local ModifiedValue = self:GetAttribute(ModifiedAttribute);
	if(not ModifiedValue) then
		ModifiedValue = self:GetAttribute(("*%s%s"):format(Attribute, self:GetModifiedSuffix()));
	end
	--FlexBar3:Debug("Get: ", ModifiedAttribute, " = ", ModifiedValue);
	return ModifiedValue;
end

-- Sets an attribute modified with the given prefix and suffix, if no prefix and/or suffix were passed, the ones currently applicable to the button are used
function AttributeClass:SetModifiedAttribute(Attribute, Value, StateModifier)
	-- sanity check, Attribute must be a string
	assert(type(Attribute) == "string" and Attribute ~= "", ("Attribute must be a non-empty string, it was %s"):format(type(Attribute)), 2);
	local ModifiedAttribute;
	if(StateModifier) then
		ModifiedAttribute = StateModifier:format(Attribute);
	else
		local Prefix = self:GetModifiedPrefix();
		ModifiedAttribute = ("%s%s%s"):format(Prefix ~= "" and Prefix or "*", Attribute, self:GetModifiedSuffix());
	end
	--FlexBar3:Debug("Set: ", ModifiedAttribute, " = ", Value);
	return self:SetAttribute(ModifiedAttribute, Value);
end

AttributeClass.GetStateAttribute = SecureState_GetStateAttribute;

function AttributeClass:SetStateAttribute(State, Attribute, Value)
	-- sanity check, Attribute must be a string
	assert(type(Attribute) == "string", ("Attribute must be a string, it was %s"):format(type(Attribute)), 2);
	assert(type(State) == "string", ("State must be a string, it was %s"):format(type(State)), 2);
	assert(type(Value) == "string", ("Value must be a string, it was %s"):format(type(Value)), 2);
	local StartPos, EndPos = State:find("%%s%-");
	if(StartPos and EndPos) then State = State:gsub(EndPos); end
	local OldAttributeValue = self:GetAttribute(Attribute);
	-- If * is given as state we assume "all states that currently exist" is ment and apply it that way
	if(State == "*") then
		local StateList = Button.StateList;
		for i = 1, #StateList do
			self:SetStateAttribute(StateList[i], Attribute, Value);
		end
	else
		for _, Clause in SemicolonIterator(OldAttributeValue) do
			local CurrentState, CurrentValue = strsplit(":", Clause);
			if(CurrentState == State) then
				self:SetAttribute(Attribute, OldAttributeValue:gsub(Clause:gsub("%-", "%%%-"), ("%s:%s"):format(State, Value)));
				return true;
			end
		end
		-- If we get this far the state was not found in the list, add it.
		if(OldAttributeValue) then
			self:SetAttribute(Attribute, ("%s;%s:%s"):format(OldAttributeValue, State, Value));
		else
			self:SetAttribute(Attribute, ("%s:%s"):format(State, Value));
		end
		return true;
	end
end

function AttributeClass:GetModifiedState()
	local Prefix = self:GetModifiedPrefix();
	return (Prefix ~= "" and Prefix or "*") .. self:GetUnmodifiedState();
end

function AttributeClass:GetUnmodifiedState()
	return "%s-" .. self.StateList[SecureStateDriverFrame.State];
end

local function StatesAreEqual(tbl1, tbl2)
	for Key, Value in pairs(tbl1) do
		if(Value and not tbl2[Key]) then return false; end
	end
	for Key, Value in pairs(tbl2) do
		if(Value and not tbl1[Key]) then return false; end
	end
	return true;
end

function AttributeClass:GenerateStateButton()
	local StateButton;
	local StateButton2;
	local StateButton3;
	local TypeOrder = Button.TypeOrder;
	local RemapInfo = self.DB.Remaps;
	local Generate = true;
	-- Check the statebutton cache if a statebutton equal to this one doesn't already have been generated, use it if it has instead of bothering to generate it again
	for Key, StateButtons in pairs(Button.StateButtonCache) do
		if(StatesAreEqual(Key, RemapInfo)) then
			Generate = false;
			StateButton = StateButtons[1];
			StateButton2 = StateButtons[2];
			StateButton3 = StateButtons[3];
			break;
		end
	end
	if(Generate) then
		local StateList = {};
		self.StateList = StateList;
		StateButton = "";
		StateButton2 = "";
		StateButton3 = "";
		for MainKey = 1, #TypeOrder do
			local Type = TypeOrder[MainKey];
			local States = Button.States[Type];
			local CurrStateList = {};
			local RemapName = TypeOrder[MainKey];
			for SubKey = 1, #States do
				local StateTable = States[SubKey];
				local State = StateTable[1];
				local Absolute = StateTable[3];
				local Remap = RemapInfo[RemapName] or false;
				local FallBackState = States[#States][1];
				-- Consider old states and add to them
				for PrecStateName, PrecStateRemap in pairs(StateList) do
					local SubStateTable = StateList[Key];
					CurrStateList[PrecStateName .. "-" .. State] = Remap and PrecStateRemap .. "-" .. State or PrecStateRemap .. (Absolute and ("-" .. FallBackState) or "");

					-- Get rid of the old state in favor of the new one
					CurrStateList[PrecStateName] = false;
				end
				-- If this is the first time we loop, then we can add the "pure" state name into the list
				if(MainKey == 1 and Absolute) then CurrStateList[State] = Remap and State or FallBackState; end
			end
			-- Merge the CurrStateList into the main one
			for StateName, MapName in pairs(CurrStateList) do
				if(MapName) then
					StateList[StateName] = MapName;
				else
					StateList[StateName] = nil;
				end
			end
		end
		for StateName, MapName in pairs(StateList) do
			StateButton = ("%s%s:%s;"):format(StateButton, StateName, MapName);
			StateButton2 = ("%s%s:RightButton-%s;"):format(StateButton2, StateName, MapName);
			StateButton3 = ("%s%s:MiddleButton-%s;"):format(StateButton3, StateName ,MapName);
		end
		-- Store the generated statebuttons in cache for later reusage
		Button.StateButtonCache[RemapInfo] = {StateButton, StateButton2, StateButton3};
		-- Generate a list of active states for this button
		local ActiveStateList = {};
		self.ActiveStateList = ActiveStateList;
		local InsertedStates = {};
		for StateName, MapName in pairs(StateList) do
			if(not InsertedStates[MapName]) then
				tinsert(ActiveStateList, MapName);
				InsertedStates[MapName] = true;
			end
		end
	end
	self:SetAttribute("statebutton", StateButton, false);
	self:SetAttribute("statebutton2", StateButton2, false);
	self:SetAttribute("statebutton3", StateButton3, false);
	self:TriggerCallback("StatesUpdated");
end
